package ru.niir.goodfil.ui;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.apache.myfaces.custom.fileupload.UploadedFile;
import org.apache.poi.hssf.OldExcelFormatException;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.poifs.filesystem.OfficeXmlFileException;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import ru.niir.goodfil.audit.domain.OrderUIEvent;
import ru.niir.goodfil.audit.domain.enums.OrderUIEventType;
import ru.niir.goodfil.db.*;
import ru.niir.goodfil.db.dic.OrderState;
import ru.niir.goodfil.exceptions.CreateFailureException;
import ru.niir.goodfil.exceptions.ValidationException;
import ru.niir.goodfil.utils.PriceInfo;
import ru.niir.goodfil.utils.validation.CreateCheckResult;
import ru.niir.goodfil.utils.validation.ValidationError;
import ru.niir.goodfil.utils.validation.ValidationResult;

import javax.faces.model.SelectItem;
import java.io.IOException;
import java.util.*;

import static org.apache.commons.lang.StringUtils.isBlank;
import static ru.niir.goodfil.ui.Utils.setReservationDate;

public class ImportOrderBean extends PageBean {
    private static final Logger logger = Logger.getLogger(ImportOrderBean.class);

    public List<SelectItem> getGoodClients() {
        List<SelectItem> result = new ArrayList<SelectItem>();
        List<Client> allClients = gf().getClients();
        for (Client client : allClients) {
            if (!client.isActive()) continue;
            if (client.getC1CodeIsMissing()) continue;
            if (client.getManagerId() == null) continue;

            // Чтобы в список попадали только клиенты данного менеджера
            if (getSecurityBean().isManagerCorrect(client.getManagerId())) {
                result.add(new SelectItem(client.getId(), client.getCompanyName()));
            }
        }
        return result;
    }

    private long clientId;

    public long getClientId() {
        return clientId;
    }

    public void setClientId(long clientId) {
        this.clientId = clientId;
    }

    private UploadedFile uploadedFile;

    public UploadedFile getUploadedFile() {
        return uploadedFile;
    }

    public void setUploadedFile(UploadedFile uploadedFile) {
        this.uploadedFile = uploadedFile;
    }

    //  Удалять отсутствующие позиции
    private boolean removeAbsentPositions;

    public boolean isRemoveAbsentPositions() {
        return removeAbsentPositions;
    }

    public void setRemoveAbsentPositions(boolean removeAbsentPositions) {
        this.removeAbsentPositions = removeAbsentPositions;
    }

    //  Идентификатор только что созданного заказа (для реализации возможности перейти к заказу после загрузки из Excel)
    private long loadedOrderId = -1;

    public long getLoadedOrderId() {
        return loadedOrderId;
    }

    public void setLoadedOrderId(long loadedOrderId) {
        this.loadedOrderId = loadedOrderId;
    }

    private long loadedOrderClientId = -1;

    public long getLoadedOrderClientId() {
        return loadedOrderClientId;
    }

    public void setLoadedOrderClientId(long loadedOrderClientId) {
        this.loadedOrderClientId = loadedOrderClientId;
    }

    public static class Item {
        public final String goodCode;
        public int count;
        public float price;

        Item(String goodCode, int count, float price) {
            this.goodCode = goodCode;
            this.count = count;
            this.price = price;
        }
    }

    //  Сообщения о нехватке остатков для данного заказа
    private List<Map<String, String>> restsProblems = new ArrayList<Map<String, String>>();

    public List<Map<String, String>> getRestsProblems() {
        Collections.sort(restsProblems, new Comparator<Map<String, String>>() {
            public int compare(Map<String, String> m1, Map<String, String> m2) {
                return m1.get("code").compareTo(m2.get("code"));
            }
        });
        return restsProblems;
    }

    public void setRestsProblems(List<Map<String, String>> restsProblems) {
        this.restsProblems = restsProblems;
    }

    /**
     * Загрузка и разбор файла с заказом из 1С
     */
    public void upload() {
        try {
            restsProblems.clear();

            //  Проверяем, что указан корректный клиент
            Client client = gf().getClientById(clientId);
            if (client == null) {
                facesError("Указан неверный клиент");
                return;
            }
            if (!client.isActive()) {
                facesError("Оформление заказа за неактивного клиента не допускается");
                return;
            }
            if (client.getC1CodeIsMissing()) {
                facesError("Указанный клиент не задан код в 1С");
                return;
            }
            if (client.getManagerId() == null) {
                facesError("Для указанного клиент не выбран менеджер");
                return;
            }

            //  Проверяем, что заказ может быть создан с учетом механизма товарного кредите
            if (!gf().clientCanSaveOrder(clientId)) {
                facesError("Долг данного клиента не допускает оформления заказа за него");
                return;
            }
            //  ----------------------------------------------------------------------------------------


            //  Проверяем, что выбран файл
            if (uploadedFile == null || uploadedFile.getInputStream() == null) {
                facesError("Не выбран файл для загрузки");
                return;
            }

            Map<String, Item> items = new HashMap<String, Item>();

            //
            //  Загружаем данные из файла и проверяем формат значений
            //

            Workbook wb = new HSSFWorkbook(uploadedFile.getInputStream());
            Sheet sheet1 = wb.getSheetAt(0);

            //  Проверяем, что лист не пустой
            try {
                String s1 = sheet1.getRow(1).getCell(0).getStringCellValue();
                if (isBlank(s1)) throw new NullPointerException();
            } catch (NullPointerException e) {
                facesError("Неверный формат файла");
                return;
            }

            //
            //  Определяем формат файла
            //
            int FIRST_ROW = -1;
            int COL_CODE = -1;
            int COL_COUNT = -1;
            int COL_PRICE = -1;

            try {
                if (sheet1.getRow(0).getCell(0).getStringCellValue().trim().equals("ОСТАТКИ *")) {
                    logger.info("Format: new");
                    FIRST_ROW = 6;
                    COL_CODE = 1;
                    COL_COUNT = 7;
                } else {
                    logger.info("Format: old");
                    FIRST_ROW = 1;
                    COL_CODE = 0;
                    COL_COUNT = 1;
                    COL_PRICE = 2;
                }
            } catch (Exception e) {
                logger.info("Format exception: old");
                FIRST_ROW = 1;
                COL_CODE = 0;
                COL_COUNT = 1;
                COL_PRICE = 2;
            }

            //
            //  Проверяем, что нет незаполненных ячеек ---------------------------------------------------------------------
            //

            for (int i = FIRST_ROW; i <= sheet1.getLastRowNum(); i++) {
                Row row = sheet1.getRow(i);
                if (row == null) facesError("Пустая строка: " + i);

                Cell cell0 = row.getCell(COL_CODE);
                Cell cell1 = row.getCell(COL_COUNT);

                if (cell0 == null) facesError("Пустая ячейка в первой колонке строки #" + (i + 1));
                if (cell1 == null) facesError("Пустая ячейка во второй колонке строки #" + (i + 1));
            }

            if (hasFacesErrors()) return;

            //  ------------------------------------------------------------------------------------------------------------

            for (int i = FIRST_ROW; i <= sheet1.getLastRowNum(); i++) {
                Row row = sheet1.getRow(i);

                Cell cell0 = row.getCell(COL_CODE);
                Cell cell1 = row.getCell(COL_COUNT);
                Cell cell2 = row.getCell(COL_PRICE);

                String goodCode = null;
                int count = 0;
                float price = 0;

                try {
                    if (cell0.getCellType() == Cell.CELL_TYPE_STRING) {
                        goodCode = cell0.getStringCellValue();
                        goodCode = GoodfilUtils.normalizeGoodCode(goodCode);

                    } else {
                        facesError("Все ячейки первой колонки (\"Код\") должны иметь строковый тип");
                    }

                    if (cell1.getCellType() == Cell.CELL_TYPE_STRING) {
                        count = Integer.parseInt(cell1.getStringCellValue());
                    } else if (cell1.getCellType() == Cell.CELL_TYPE_NUMERIC) {
                        count = (int) cell1.getNumericCellValue();
                        if (count == 0) continue;
                    } else {
                        facesError("Все ячейки второй колонки (\"Количество\") должны иметь строковый или числовой тип");
                        return;
                    }

                    if (cell2 != null) {
                        if (cell2.getCellType() == Cell.CELL_TYPE_STRING) {
                            price = Float.parseFloat(cell2.getStringCellValue());
                        } else if (cell2.getCellType() == Cell.CELL_TYPE_NUMERIC) {
                            price = (float) cell2.getNumericCellValue();
                        } else {
                            facesError("Все ячейки третьей колонки (\"Цена\") должны иметь строковый или числовой тип");
                            return;
                        }
                    }

                    if (StringUtils.isBlank(goodCode)) {
                        facesError("Строка " + String.valueOf(i + 1) + ": неверное значение ячейки \"Код\"");
                        return;
                    }
                    if (count <= 0) {
                        facesError("Строка " + String.valueOf(i + 1) + ": неверное значение ячейки \"Количество\"");
                        return;
                    }
                    if (price < 0) {
                        facesError("Строка " + String.valueOf(i + 1) + ": неверное значение ячейки \"Цена\"");
                        return;
                    }

                    if (!items.containsKey(goodCode)) {
                        items.put(goodCode, new Item(goodCode, count, price));
                    } else {
                        Item item = items.get(goodCode);
                        if (item.price != price) {
                            facesError("Для товара " + goodCode + " указана разная стоимость в разных строках");
                            return;
                        }
                        item.count += count;
                    }
                } catch (NumberFormatException nfe) {
                    facesError("Ошибка в строчке " + String.valueOf(i + 1));
                }
            }

            //
            //  Проверяем загруженную из файла накладную
            //
            //  Нужно проверить, что:
            //      - такой товар существует
            //      - указанное количество товара доступно
            for (Item item : items.values()) {
                Good good = gf().getGoodByCode(item.goodCode);
                if (good == null) {
                    facesError("В ИНТЕРНЕТ-системе отсутствует товар с кодом " + item.goodCode);
                } else {
                    if (good.isDeleted())
                        facesError("Товар с кодом " + item.goodCode + " находится в списке удаленных");
                    if (good.getC1CodeIsMissing())
                        facesError("Товар с кодом " + item.goodCode + " не может быть передан в 1С");

                    if (item.count > (good.getCount() - good.getReservedCount())) {
                        Map<String, String> restProblem = new HashMap<String, String>();
                        restProblem.put("code", good.getCode());
                        restProblem.put("requestedCount", String.valueOf(item.count));
                        restProblem.put("count", String.valueOf(good.getCount() - good.getReservedCount()));
                        restProblem.put("price", String.valueOf(gf().getPrice2(good.getId(), good.getBrandId(), clientId, gf().getClientById(clientId).getClientgroupIdByBrandId(1)).getPrice()));
                        restsProblems.add(restProblem);

                        if (removeAbsentPositions) {   //  Изменяем позицию заказа
                            item.count = good.getCount() - good.getReservedCount();
                            if (item.count < 0) item.count = 0;
                        }
                    }
                }
            }

            if (restsProblems.size() > 0 && !removeAbsentPositions)
                facesError("Заказ невозможно провести из-за нехватке остатков по некоторым позициям (см. ниже)");


            if (hasFacesErrors()) return;

            /*
             Если ошибок нет, то фиксируется недосдача товара
             */
            if(restsProblems.size()>0){
                gf().saveGoodAbsences(restsProblems,clientId);
            }
            //  Теперь все ок, можно создавать заказ
            Order order = new Order();
            setReservationDate(order, gf());
            order.setClientId(clientId);
            order.setManagerId(client.getManagerId());

            client.getDeliveryInfoExt().copyTo(order.getDeliveryInfoExt());
            Calendar deliveryDate = Calendar.getInstance();
            deliveryDate.add(Calendar.DAY_OF_MONTH, order.getDeliveryInfoExt().getPeriod());
            order.setDeliveryDate(deliveryDate.getTime());

            gf().createIfNotExists("deliveryTimeSpan", 7);
            int deliveryTimeSpan = gf().getInt("deliveryTimeSpan");
            order.setDeliveryTypeId(client.getDeliveryTypeId());
            order.setDeliveryAddress(client.getAddressDlv());
            order.setDeliveryTimeSpan(deliveryTimeSpan);
            order.setDeliverySum(0);
            if(gf().getIsReservedOn()){
                order.setOrderStateId(OrderState.AWAITING_VALIDATION);
            }

            List<OrderItem> orderItems = new ArrayList<OrderItem>();
            for (Item item : items.values()) {
                if (item.count <= 0) continue;

                Good good = gf().getGoodByCode(item.goodCode);
                PriceInfo pi = gf().getPrice2(good, client);

                logger.debug("Price for good " + good.getCode() + ": " + pi);

                OrderItem orderItem = new OrderItem();
                orderItem.setGood(good);
                orderItem.setGoodId(good.getId());
                orderItem.setCount(item.count);
                orderItem.setStartPrice(pi.getPrice());
                orderItem.setPersonalPrice(pi.isPersonalPrice());
                if (item.price > pi.getPrice())
                    orderItem.setManualPrice(item.price);

                orderItems.add(orderItem);
            }

            for (OrderItem orderItem : orderItems) {
                if (orderItem.getStartPrice() == 0)
                    facesError(orderItem.getGood().getCode() + ": неверная цена в прайсе");
            }

            if (hasFacesErrors()) return;

            try {
                gf().saveOrder(order);
            } catch (ValidationException ex) {
                logger.error(ex);
                ex.printStackTrace();
                ValidationResult validationResult = ex.getResult();
                for (ValidationError error : validationResult.getErrors()) {
                    facesError(error.toHumanString());
                }
            } catch (CreateFailureException ex) {
                logger.error(ex);
                ex.printStackTrace();
                CreateCheckResult createCheckResult = ex.getResult();
                facesError(createCheckResult.getMsg());
            }

            //  Возможно, тут есть проблемы с транзакционностью
            for (OrderItem orderItem : orderItems) {
                try {
                    orderItem.setOrderId(order.getId());
                    gf().saveOrderItem(orderItem);
                    if(gf().getIsReservedOn()){
                        Good good = orderItem.getGood();
                        if((good.getCount() - good.getReservedCount()) < orderItem.getCount()){
                            facesError("Невозможно зарезервировать позиции");
                            return;
                        }
                        good.setReservedCount(good.getReservedCount() + orderItem.getCount());
                        try {
                            gf().updateGood(good);
                        } catch (ValidationException e) {
                            logger.error(e.getMessage());
                            facesError("Произошла ошибка. Пожалуйста, свяжитесь с администратором");
                        }
                    }
                } catch (ValidationException ex) {
                    logger.error(ex);
                    ex.printStackTrace();
                    ValidationResult validationResult = ex.getResult();
                    for (ValidationError error : validationResult.getErrors()) {
                        facesError(error.toHumanString());
                    }
                } catch (CreateFailureException ex) {
                    logger.error(ex);
                    ex.printStackTrace();
                    CreateCheckResult createCheckResult = ex.getResult();
                    facesError(createCheckResult.getMsg());
                }
            }

            /*{
                // Если резервы включены, то поставить позиции в резерв
                if(gf().getIsReservedOn()){
                    for(OrderItem orderItem : orderItems){
                        Good good = orderItem.getGood();
                        if((good.getCount() - good.getReservedCount()) < orderItem.getCount()){
                            facesError("Невозможно зарезервировать позиции");
                            return;
                        }
                        good.setReservedCount(good.getReservedCount() + orderItem.getCount());
                        try {
                            gf().updateGood(good);
                        } catch (ValidationException e) {
                            logger.error(e.getMessage());
                            facesError("Произошла ошибка. Пожалуйста, свяжитесь с администратором");
                        }
                    }
                }
            }
*/
            loadedOrderId = order.getId();
            loadedOrderClientId = order.getClientId();

            gf().addOrderInfo(order);
            getAuditService().logOrderUIEvent(prepareOrderUIEvent(OrderUIEventType.CREATED_FROM_EXCEL, order));

            facesInfo("Заказ сохранен");
            if (restsProblems.size() > 0) facesInfo("Ряд позиций были изменены/удалены");
        }catch (IOException e) {
            e.printStackTrace();
        }catch (OfficeXmlFileException e){
            facesError("Невозможно чтение файла Excel 2007 +. Пожалуйста, выберите файл, сохраненный в формате Excel 2003.");
            logger.error(e.getMessage());
        }catch (OldExcelFormatException e){
            facesError("Файл сохранен в старой версии Excel. Пожалуйста, выберите файл, сохраненный в формате Excel 2003.");
            logger.error(e.getMessage());
        }
    }

    private OrderUIEvent prepareOrderUIEvent(OrderUIEventType type, Order order) {
        OrderUIEvent event = new OrderUIEvent();

        event.setIp(getAuditHelper().getIp());
        event.setLogin(getAuditHelper().getLogin());
        event.setType(type);

        event.setOrderId(String.valueOf(order.getId()));
        event.setC1Num(order.getC1Num());

        Client client = order.getClient();
        if (client == null) client = gf().getClientById(order.getClientId());
        event.setClientId(String.valueOf(client.getId()));
        event.setClientName(client.getCompanyName());

        User manager = order.getManager();
        if (manager == null) manager = gf().getUserById(order.getManagerId());
        event.setManagerId(String.valueOf(manager.getId()));
        event.setManagerName(manager.getFio());

        event.setCreationDate(order.getCreationDateAsString());
        event.setDeliveryDate(order.getDeliveryDateAsString());
        event.setOrderSum(String.valueOf(order.getSum()));

        return event;
    }
}